/*
 * Copyright (c) 2011 Imre Fazekas.
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * Neither the name of the Brillien nor the names of its
 * terms and concepts may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

package com.vii.brillien.ignition.transport.jsonrpc;

import com.vii.brillien.ignition.BrillienContext;
import com.vii.brillien.ignition.transport.BrillienCommunication;
import com.vii.brillien.ignition.transport.BrillienMediator;
import com.vii.brillien.ignition.transport.TransportStatics;
import com.vii.brillien.kernel.BrillienException;
import com.vii.brillien.kernel.axiom.transport.Communication;
import com.vii.brillien.kernel.axiom.transport.MessageProcessor;
import com.vii.streamline.json.JsonRpcRequest;
import com.vii.streamline.services.ThreadServices;
import com.vii.streamline.services.error.StreamLineException;
import com.vii.streamline.structures.collections.InnerList;
import com.vii.streamline.web.HttpServices;

import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import static com.vii.streamline.jsonrpc.exchange.ExchangeStatics.*;

/**
 * Specialized Mediator dispatching all communication via JSON-RPC 2.0 calls.
 * The {@JsonRpcRequest} objects will be sent to a proper endpoint represented by a URL field of this class' instance.
 * A proper endpoint is provided by the {@ExchangeServlet} and {@ExchangeJsonRpcListener} classes of Brillien.
 */
public class JsonRpcMediator extends BrillienMediator<JsonRpcRequest> {

    protected String                    queuename;
    protected String                    url;

    protected JsonRpcPoller             listener;

    /**
     * Map of queue to catch responses.
     */
    protected HashMap<String, LinkedBlockingQueue<BrillienCommunication>> responseQueues;

    {
        url = "http://localhost:8080/jsonrpc/ExchangeServlet";

        responseQueues = new HashMap<String, LinkedBlockingQueue<BrillienCommunication>>();
    }

    public JsonRpcMediator() {
    }

    public JsonRpcMediator(String url, String queuename) {
        this.url = url;
        this.queuename = queuename;
    }

    /**
     * Get method for field url
     */
    public String getUrl() {
        return url;
    }

    /**
     * Set method for field url
     */
    public void setUrl(String url) {
        this.url = url;
    }

    /**
     * Set method for field queuename
     */
    public String getQueuename() {
        return queuename;
    }

    /**
     * Set method for field queuename
     */
    public void setQueuename(String queuename) {
        this.queuename = queuename;
    }

    /**
     * Get method for field responseQueues
     */
    public HashMap<String, LinkedBlockingQueue<BrillienCommunication>> getResponseQueues() {
        return responseQueues;
    }

    @Override
    public JsonRpcCommunication newCommunication() throws BrillienException {
        return new JsonRpcCommunication();
    }

    @Override
    public void connect() throws BrillienException {
        HttpServices.initWebClient( BrillienContext.RECONNECTION_ATTEMPT, HttpServices.MAX_CONNECTIONS );

        BrillienContext.basicLog(Level.FINE, mediatorEntity() + ":: " + "Connected. ");

        startListening();
    }

    @Override
    public void disconnect() throws BrillienException {
        listener.setListen(false);

        BrillienContext.basicLog(Level.FINE, mediatorEntity() + ":: " + "Disconnected.");
    }

    @Override
    public void startListening() throws BrillienException {
        BrillienContext.basicLog(Level.FINE, mediatorEntity() + ":: " + "Started listening...");

        listener = new JsonRpcPoller(this, queuename);
        ThreadServices.getStpe().schedule(
                listener, TransportStatics.POLLING_INTERVAL, TimeUnit.MILLISECONDS
        );
    }

    @Override
    public String mediatorEntity() {
        return queuename;
    }

    @Override
    public BrillienCommunication sendCommunication(long timeout, HashMap<String, Object> properties, BrillienCommunication comm) throws BrillienException {
        try {
            JsonRpcCommunication resp = HttpServices.<JsonRpcCommunication>sendJsonRpcMessage(
                    url,
                    JSON_RPC_METHOD_PREFIX + JSON_RPC_METHOD_SEPARATOR + JSON_RPC_METHOD_CALL_POSTFIX,
                    new InnerList(comm), ".result", JsonRpcCommunication.class
            );
            if (comm.getMessageType() == Communication.TYPE_GET) {
                LinkedBlockingQueue<BrillienCommunication> queue = new LinkedBlockingQueue<BrillienCommunication>(1);
                responseQueues.put(comm.getOriginalId(), queue);

                BrillienContext.basicLog(Level.FINE, mediatorEntity() + ":: " + "waiting for response for " + comm);

                try {
                    BrillienCommunication response = responseQueues.get(comm.getOriginalId()).poll(timeout, TimeUnit.MILLISECONDS);

                    BrillienContext.basicLog(Level.FINE, mediatorEntity() + ":: " + "response received " + response + " to " + comm);

                    if (response == null) {
                        throw new BrillienException("No response from server for communication: " + comm);
                    } else if (response.getMessageType() == Communication.TYPE_ERROR) {
                        throw new BrillienException(response.getResponse().toString());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    responseQueues.remove( comm.getOriginalId() );
                }
            }
        } catch (StreamLineException e) {
            throw new BrillienException(e);
        }
        return null;
    }

    @Override
    public void receiveCommunication(BrillienCommunication comm) throws BrillienException {
        BrillienContext.basicLog( Level.FINE, mediatorEntity() + ":: " + "internal communication received " + comm );

        if( comm.getMessageType() == Communication.TYPE_RESULT || comm.getMessageType() == Communication.TYPE_ERROR )
            if( responseQueues.containsKey( comm.getOriginalId() ) )
                try{
                    responseQueues.get( comm.getOriginalId() ).offer( comm );
                } catch (Exception e){ }

        for( MessageProcessor processor : getNewMessageProcessorInstances( comm ) ){
            ThreadServices.getStpe().execute( processor );
        }
    }

}
